{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "22309a5a",
      "metadata": {},
      "source": [
        "可以在[Bookshop.org](https://bookshop.org/a/98697/9781098155438) 和\n",
        "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)获取纸制版和电子版的*Think Python 3e*."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "3161b50b",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "from os.path import basename, exists\n",
        "\n",
        "def download(url):\n",
        "    filename = basename(url)\n",
        "    if not exists(filename):\n",
        "        from urllib.request import urlretrieve\n",
        "\n",
        "        local, _ = urlretrieve(url, filename)\n",
        "        print(\"Downloaded \" + str(local))\n",
        "    return filename\n",
        "\n",
        "download('https://gitee.com/regentsai/Think_Python_3e_CN/blob/master/thinkpython.py');\n",
        "download('https://gitee.com/regentsai/Think_Python_3e_CN/blob/master/diagram.py');\n",
        "\n",
        "import thinkpython"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fa22117f",
      "metadata": {},
      "source": [
        "# 第15章:类和方法\n",
        "\n",
        "Python是一个 **面向对象语言** -- 也就是说，它提供了支持面向对象编程的特性，这些特性具有以下特性:\n",
        "\n",
        "- 大多数计算是根据对对象的操作来表示的。\n",
        "- 对象通常代表现实世界中的事物，方法通常对应于现实世界中的事物交互的方式。\n",
        "- 程序包括类和方法定义。\n",
        "\n",
        "例如，在上一章中，我们定义了一个 `Time` 类，它对应于人们记录一天中的时间的方式，我们定义了与人们处理时间的方式相对应的函数,但没有明确`Time` 类和随后的函数之间的联系。\n",
        "\n",
        "我们可以通过将函数重写为 **方法 method** ,它是在类定义中定义的。"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9857823a",
      "metadata": {},
      "source": [
        "## 定义方法\n",
        "\n",
        "In the previous chapter we defined a class named `Time` and wrote a function named `print_time` that displays a time of day.\n",
        "\n",
        "在上一章中，我们定义了一个名为 `Time`的类并编写了一个名为 `print_time`的函数来显示一天中的时间。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "ee093ca4",
      "metadata": {},
      "outputs": [],
      "source": [
        "class Time:\n",
        "    \"\"\"代表一天中的时刻.\"\"\"\n",
        "\n",
        "def print_time(time):\n",
        "    s = f'{time.hour:02d}:{time.minute:02d}:{time.second:02d}'\n",
        "    print(s)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a89ddf58",
      "metadata": {},
      "source": [
        "要创建`print_time`方法，我们所要做的只是移动函数定义到类定义中.注意缩进的变化.\n",
        "\n",
        "同时，我们将参数的名称从 `time` 至 `self` 。此更改不是必需的，但通常将方法的第一个参数命名为 `self` 。\n",
        "\n",
        "译注:方法的第1个参数`self`表示这个类的一个实例自身.在特殊情况下,这个参数名可能是其他名字.从语法上讲,方法的第1个参数只是占位符,它的名字不重要."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "fd26a1bc",
      "metadata": {},
      "outputs": [],
      "source": [
        "class Time:\n",
        "    \"\"\"代表一天中的时刻.\"\"\"    \n",
        "\n",
        "    def print_time(self):\n",
        "        s = f'{self.hour:02d}:{self.minute:02d}:{self.second:02d}'\n",
        "        print(s)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8da4079c",
      "metadata": {},
      "source": [
        "要调用此方法，必须传递一个 `Time` 对象作为参数。\n",
        "下面是创建一个`Time` 对象使用的函数。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "5fc157ea",
      "metadata": {},
      "outputs": [],
      "source": [
        "def make_time(hour, minute, second):\n",
        "    time = Time()\n",
        "    time.hour = hour\n",
        "    time.minute = minute\n",
        "    time.second = second\n",
        "    return time"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c6ad4e12",
      "metadata": {},
      "source": [
        "以及这是一个 `Time` 实例。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "35acd8e6",
      "metadata": {},
      "outputs": [],
      "source": [
        "start = make_time(9, 40, 0)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bbbcd333",
      "metadata": {},
      "source": [
        "有两种方法可以调用 `print_time` 。第一个 (不太常见)的方式是使用函数语法。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "f755081c",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "09:40:00\n"
          ]
        }
      ],
      "source": [
        "Time.print_time(start)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2eb0847e",
      "metadata": {},
      "source": [
        "在这个版本中， `Time` 是类的名称， `print_time` 是方法的名称，而 `start` 作为参数传递。\n",
        "第二种 (也是更惯用的)方式是使用方法语法:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "d6f91aec",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "09:40:00\n"
          ]
        }
      ],
      "source": [
        "start.print_time()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c80c40f0",
      "metadata": {},
      "source": [
        "在这个版本中， `start` 是调用方法的对象，称为 **接收器 receiver** ,因为调用方法就像给对象发送消息。\n",
        "\n",
        "无论语法如何，方法的行为都是相同的。接收器被赋值给第一个形参，因此在方法内部， `self` 指向相同的对象 `start` 。"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8deb6c34",
      "metadata": {},
      "source": [
        "## 另一个方法\n",
        "\n",
        "这是上一章的 `time_to_int` 函数。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "24c4c985",
      "metadata": {},
      "outputs": [],
      "source": [
        "def time_to_int(time):\n",
        "    minutes = time.hour * 60 + time.minute\n",
        "    seconds = minutes * 60 + time.second\n",
        "    return seconds"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "144e043f",
      "metadata": {},
      "source": [
        "这里是一个重写为方法的版本。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "dde6f15f",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Time\n",
        "\n",
        "    def time_to_int(self):\n",
        "        minutes = self.hour * 60 + self.minute\n",
        "        seconds = minutes * 60 + self.second\n",
        "        return seconds"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e3a721ab",
      "metadata": {},
      "source": [
        "The first line uses the special command `add_method_to`, which adds a method to a previously-defined class.\n",
        "This command works in a Jupyter notebook, but it is not part of Python, so it won't work in other environments.\n",
        "Normally, all methods of a class are inside the class definition, so they get defined at the same time as the class.\n",
        "But for this book, it is helpful to define one method at a time.\n",
        "\n",
        "第一行使用特殊命令 `add_method_to` ,它向先前定义的类添加一个方法。\n",
        "此命令在Jupyter notebook中工作，但它不是Python的一部分，因此在其他环境中不起作用。\n",
        "\n",
        "通常，类的所有方法都在类定义中，因此它们与类同时定义。但是对于这本书，一次定义一种方法是有帮助的。\n",
        "\n",
        "译注:由于python的动态特性,上一章可以在类定义外新增属性,其实也可以在类定义外新增方法(语法与新增属性类似,因为方法本身也是一种特殊的可调用的属性).这种额外拓展的补丁在插件,第三方库临时修复等方面有所应用.\n",
        "\n",
        "与前面的示例一样，方法定义是缩进的，参数的名称是 `self` 。除此之外，该方法与原函数相同。以下是调用它的方式。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "8943fa0a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "34800"
            ]
          },
          "execution_count": 10,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "start.time_to_int()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "14565505",
      "metadata": {},
      "source": [
        "通常说我们 “调用call” 一个函数和 “调用invoke” 一个方法，但它们的意思是一样的。"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7bc24683",
      "metadata": {},
      "source": [
        "## 静态方法\n",
        "\n",
        "作为另一个例子，让我们考虑 `int_to_time` 功能。以下是上一章的版本。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "8547b1c2",
      "metadata": {},
      "outputs": [],
      "source": [
        "def int_to_time(seconds):\n",
        "    minute, second = divmod(seconds, 60)\n",
        "    hour, minute = divmod(minute, 60)\n",
        "    return make_time(hour, minute, second)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2b77c2a0",
      "metadata": {},
      "source": [
        "此函数需要 `seconds` 作为参数，并返回一个新的 `Time` 对象。\n",
        "如果我们将其转换为 `Time` 的属性，我们必须在一个 `Time` 对象上调用。\n",
        "但是如果我们试图创造一个新的 `Time` 对象，我们应该在什么对象上调用它？\n",
        "\n",
        "We can solve this chicken-and-egg problem using a **static method**, which is a method that does not require an instance of the class to be invoked.\n",
        "Here's how we rewrite this function as a static method.\n",
        "\n",
        "我们可以用 **静态方法 static method** ,这是一种不需要实例的方法。下面是我们如何将这个函数重写为静态方法。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "b233669c",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Time\n",
        "\n",
        "    def int_to_time(seconds):\n",
        "        minute, second = divmod(seconds, 60)\n",
        "        hour, minute = divmod(minute, 60)\n",
        "        return make_time(hour, minute, second)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a7e2e788",
      "metadata": {},
      "source": [
        "因为它是一个静态方法，它没有 `self` 作为参数。\n",
        "要调用它，我们使用 `Time` 这个是类对象。\n",
        "\n",
        "译注:静态方法是与某个类关联的方法,与具体的实例无关；普通的方法是与某个实例关联的方法,通常需要用到或修改实例的属性.\n",
        "\n",
        "译注:要在类定义体中定义静态方法的话,为了明确用途,必须使用@staticmethod来装饰这个方法.即:\n",
        "\n",
        "```py\n",
        "class Time:\n",
        "    @staticmethod\n",
        "    def int_to_time(seconds):...\n",
        "```"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "7e88f06b",
      "metadata": {},
      "outputs": [],
      "source": [
        "start = Time.int_to_time(34800)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d2f4fd5a",
      "metadata": {},
      "source": [
        "结果是一个表示9:40的新对象。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "8c9f66b0",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "09:40:00\n"
          ]
        }
      ],
      "source": [
        "start.print_time()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e6a18c76",
      "metadata": {},
      "source": [
        "现在我们有了 `Time.from_seconds` ,我们可以用它来写 `add_time`方法。\n",
        "这是上一章的函数。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "c600d536",
      "metadata": {},
      "outputs": [],
      "source": [
        "def add_time(time, hours, minutes, seconds):\n",
        "    duration = make_time(hours, minutes, seconds)\n",
        "    seconds = time_to_int(time) + time_to_int(duration)\n",
        "    return int_to_time(seconds)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8e56da48",
      "metadata": {},
      "source": [
        "这是重写为方法的版本。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "c6fa0176",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Time\n",
        "\n",
        "    def add_time(self, hours, minutes, seconds):\n",
        "        duration = make_time(hours, minutes, seconds)\n",
        "        seconds = time_to_int(self) + time_to_int(duration)\n",
        "        return Time.int_to_time(seconds)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b784a4ea",
      "metadata": {},
      "source": [
        "`add_time` 有 `self` 作为参数，因为它不是静态方法。\n",
        "这是一个普通的方法-也称为 **实例方法 instance method** 。\n",
        "要调用它，我们需要一个 `Time` 实例。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "e17b2ad7",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "11:12:00\n"
          ]
        }
      ],
      "source": [
        "end = start.add_time(1, 32, 0)\n",
        "print_time(end)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f1c806a9",
      "metadata": {},
      "source": [
        "## 比较时间对象\n",
        "\n",
        "再举一个例子，让我们编写 `is_after`方法。以下是上一章中练习的 `is_after` 函数。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "971eebbb",
      "metadata": {},
      "outputs": [],
      "source": [
        "def is_after(t1, t2):\n",
        "    return time_to_int(t1) > time_to_int(t2)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8e7153e8",
      "metadata": {},
      "source": [
        "以下是方法的写法。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "90d7234d",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Time\n",
        "\n",
        "    def is_after(self, other):\n",
        "        return self.time_to_int() > other.time_to_int()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "50815aec",
      "metadata": {},
      "source": [
        "因为我们要比较两个对象，第一个参数是 `self` ,第二个参数叫做 `other` 。\n",
        "\n",
        "要使用此方法，我们必须在一个对象上调用它，并传递other作为实参。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "19e3d639",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "True"
            ]
          },
          "execution_count": 20,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "end.is_after(start)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cf97e358",
      "metadata": {},
      "source": [
        "这种语法的一个好处是，它几乎读起来像自然语言，\" `end` 是在 `start` 之后吗?\""
      ]
    },
    {
      "cell_type": "markdown",
      "id": "15a17fce",
      "metadata": {},
      "source": [
        "## `__str__` 方法\n",
        "\n",
        "当你写编写方法时，你几乎可以选择任何你想要的名字。然而，有些变量名有特殊的含义。\n",
        "\n",
        "例如，如果对象有名为 `__str__`的方法 ,Python使用该方法将对象转换为字符串。\n",
        "\n",
        "例如，以下是一个time对象的 `__str__` 方法。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "f935a999",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Time\n",
        "\n",
        "    def __str__(self):\n",
        "        s = f'{self.hour:02d}:{self.minute:02d}:{self.second:02d}'\n",
        "        return s"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b056b729",
      "metadata": {},
      "source": [
        "此方法类似于上一章的 `print_time` ，但是返回字符串而不是打印。\n",
        "\n",
        "您可以用通常的方式调用此方法。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "61d7275d",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'11:12:00'"
            ]
          },
          "execution_count": 22,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "end.__str__()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "76092a0c",
      "metadata": {},
      "source": [
        "但是Python也可以为你调用它。如果使用内置函数 `str` 将`Time` 对象转换为字符串，则Python使用`Time` 类的 `__str__` 方法。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "b6dcc0c2",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'11:12:00'"
            ]
          },
          "execution_count": 23,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "str(end)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8a26caa8",
      "metadata": {},
      "source": [
        "如果你打印一个 `Time` 对象,也会自动调用该方法。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "6e1e6fb3",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "11:12:00\n"
          ]
        }
      ],
      "source": [
        "print(end)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "97eb30c2",
      "metadata": {},
      "source": [
        "像`__str__` 的方法被称为 **特殊方法 special methods** 。可以分辨它们，因为它们的名称以两个下划线开头和结尾。"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e01e9673",
      "metadata": {},
      "source": [
        "## `__init__` 方法\n",
        "\n",
        "最特殊的特殊方法是 `__init__` ,之所以这样称呼，是因为它初始化了新对象的属性。\n",
        "`Time` 类的 `__init__` 方法的可能看起来像这样:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "id": "7ddcca8a",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Time\n",
        "\n",
        "    def __init__(self, hour=0, minute=0, second=0):\n",
        "        self.hour = hour\n",
        "        self.minute = minute\n",
        "        self.second = second"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8ba624c3",
      "metadata": {},
      "source": [
        "当我们实例化一个 `Time` 对象时，Python调用 `__init__` ,并传递参数。所以我们可以同时创建对象并初始化属性。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "id": "afd652c6",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "09:40:00\n"
          ]
        }
      ],
      "source": [
        "time = Time(9, 40, 0)\n",
        "print(time)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55e0e296",
      "metadata": {},
      "source": [
        "在此示例中，形参是可选的，因此如果调用 `Time` 没有提供实参，将获得默认值。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "8a852588",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "00:00:00\n"
          ]
        }
      ],
      "source": [
        "time = Time()\n",
        "print(time)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bacb036d",
      "metadata": {},
      "source": [
        "如果提供了一个实参，将按顺序覆盖 `hour` :"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "id": "0ff75ace",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "09:00:00\n"
          ]
        }
      ],
      "source": [
        "time = Time(9)\n",
        "print(time)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "37edb221",
      "metadata": {},
      "source": [
        "提供两个参数则覆盖 `hour` 和 `minute` 。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "id": "b8e948bc",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "09:45:00\n"
          ]
        }
      ],
      "source": [
        "time = Time(9, 45)\n",
        "print(time)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "277de217",
      "metadata": {},
      "source": [
        "如果提供三个参数则覆盖所有三个默认值。\n",
        "\n",
        "当我写一个新类时，我几乎总是首先编写 `__init__` 使得创建对象更容易，以及`__str__` 对于调试很有用。"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "94bbbd7d",
      "metadata": {},
      "source": [
        "## 运算符重载\n",
        "\n",
        "通过定义其他特殊方法，可以指定用户定义的类型上的运算符。例如，如果对`Time` 类定义了`__add__`方法，可以在间对象上使用 `+` 运算符时。\n",
        "\n",
        "以下是一个 `__add__` 方法。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 30,
      "id": "0d140036",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Time\n",
        "\n",
        "    def __add__(self, other):\n",
        "        seconds = self.time_to_int() + other.time_to_int()\n",
        "        return Time.int_to_time(seconds)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0221c9ad",
      "metadata": {},
      "source": [
        "可以像这样使用它。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "280acfce",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "11:12:00\n"
          ]
        }
      ],
      "source": [
        "duration = Time(1, 32)\n",
        "end = start + duration\n",
        "print(end)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7cc7866e",
      "metadata": {},
      "source": [
        "当运行这三行代码时，会发生很多事情:\n",
        "\n",
        "* 当实例化`Time` 对象，`__init__` 方法被调用。\n",
        "* 当对`Time` 对象使用 `+` 运算符 ，其 `__add__` 方法被调用。\n",
        "* 当打印一个 `Time` 对象，其 `__str__` 方法被调用。\n",
        "\n",
        "更改运算符的行为以使其与用户定义的类型一起工作称为 **运算符重载 operator overloading** 。\n",
        "对于每个运算符，如 `+` ,都有对应的特殊方法，如 `__add__` 。"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b7299e62",
      "metadata": {},
      "source": [
        "## 调试\n",
        "\n",
        "如果 `minute` 和 `second` 在 `0` ~ `60`,包括 `0` 但不包括 `60`,`hour` 是非负数,则`Time` 对象的值是有效的。\n",
        "\n",
        "此外， `hour` 和 `minute` 应该是整数值，但我们可能允许 `second` 有一个分数部分。\n",
        "\n",
        "像这样的需求被称为 **不变量 invariants**,因为它们应该永远是正确的。换句话说，如果它们不是真的，那就是出了问题。\n",
        "\n",
        "编写代码检查不变量可以帮助检测错误并找到其原因。\n",
        "\n",
        "例如，`is_valid`方法接受时间对象,当属性违反不变式时返回 `False` 。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "id": "6eb34442",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Time\n",
        "\n",
        "    def is_valid(self):\n",
        "        if self.hour < 0 or self.minute < 0 or self.second < 0:\n",
        "            return False\n",
        "        if self.minute >= 60 or self.second >= 60:\n",
        "            return False\n",
        "        if not isinstance(self.hour, int):\n",
        "            return False\n",
        "        if not isinstance(self.minute, int):\n",
        "            return False\n",
        "        return True"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a10ad3db",
      "metadata": {},
      "source": [
        "然后，可以在每个方法的开始检查参数以确保它们有效。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 33,
      "id": "57d86843",
      "metadata": {},
      "outputs": [],
      "source": [
        "%%add_method_to Time\n",
        "\n",
        "    def is_after(self, other):\n",
        "        assert self.is_valid(), 'self 不是有效的 Time'\n",
        "        assert other.is_valid(), 'self 不是有效的 Time'\n",
        "        return self.time_to_int() > other.time_to_int()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e7c78e9a",
      "metadata": {},
      "source": [
        "`assert` 语句计算后面的表达式。如果结果是 `True` ,它什么也不做; 如果结果是 `False` ,它会导致一个 `AssertionError` 。以下是一个例子。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 34,
      "id": "5452888b",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "00:132:00\n"
          ]
        }
      ],
      "source": [
        "duration = Time(minute=132)\n",
        "print(duration)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "id": "56680d97",
      "metadata": {
        "tags": []
      },
      "outputs": [
        {
          "ename": "AssertionError",
          "evalue": "self 不是有效的 Time",
          "output_type": "error",
          "traceback": [
            "\u001b[31mAssertionError\u001b[39m\u001b[31m:\u001b[39m self 不是有效的 Time\n"
          ]
        }
      ],
      "source": [
        "%%expect AssertionError\n",
        "\n",
        "start.is_after(duration)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "18bd34ad",
      "metadata": {},
      "source": [
        "`assert` 语句很有用，因为它们将处理正常条件的代码与检查错误的代码区分开。"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "58b86fbe",
      "metadata": {},
      "source": [
        "## 术语表\n",
        "\n",
        "- **面向对象语言 object-oriented language**:一种语言，支持面向对象编程的功能，尤其是用户定义的类型。\n",
        "- **方法method**:在类定义中定义并在该类的实例上调用的函数。\n",
        "- **接收器receiver**:调用方法的对象。\n",
        "- **静态方法static method**:可以在没有对象作为接收者的情况下调用的方法。\n",
        "- **实例方法instance method**:必须使用对象作为接收者来调用的方法。\n",
        "- **特殊方法special method**:一种方法，用于更改运算符和某些函数处理对象的方式。通常不直接调用这些方法\n",
        "- **运算符重载operator overloading**:使用特殊方法将运算符定义与用户定义类型一起更改的过程。\n",
        "- **不变量invariant**:在程序执行期间应始终为真的条件。"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "796adf5c",
      "metadata": {},
      "source": [
        "## 练习"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "id": "3115ea33",
      "metadata": {
        "tags": []
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Exception reporting mode: Verbose\n"
          ]
        }
      ],
      "source": [
        "# 这个单元格让Jupyter在出现运行时故障时提供更多调试信息。\n",
        "# 在进行练习前先运行本单元格。\n",
        "\n",
        "%xmode Verbose"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "25cd6888",
      "metadata": {},
      "source": [
        "### 询问虚拟助理\n",
        "\n",
        "有关静态方法的更多信息，请询问虚拟助手:\n",
        "\n",
        "* “实例方法和静态方法有什么区别？”\n",
        "* “为什么静态方法称为static？”\n",
        "\n",
        "如果您要求虚拟助手生成静态方法，结果可能会以 `@staticmethod` ,这是一个 “装饰器”，表示它是一个静态方法。\n",
        "装饰者不包括在这本书，但如果你好奇，你可以问VA更多信息。\n",
        "\n",
        "在本章中，我们重写了几个函数作为方法。虚拟助手通常擅长这种代码转换。\n",
        "\n",
        "例如，将以下函数粘贴到VA中，并询问它: “将此函数重写为 `Time` 类的方法。”"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "id": "133d7679",
      "metadata": {},
      "outputs": [],
      "source": [
        "def subtract_time(t1, t2):\n",
        "    return time_to_int(t1) - time_to_int(t2)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "fc9f135b-e242-4ef6-83eb-8e028235c07b",
      "metadata": {},
      "source": [
        "### 练习\n",
        "\n",
        "在上一章中，要求你写一个 `Date` 类和几个处理 `Date` 对象的函数。\n",
        "现在让我们练习重写这些函数为方法。\n",
        "\n",
        "1. 编写`Date` 类定义 ，它表示日期 (即年、月和月中的日)。\n",
        "2. 编写 `__init__` 方法，该方法接受 `year` , `month` ,和 `day` 作为参数，并将参数指定给属性。创建一个表示1933年6月22日的对象。\n",
        "3. 编写 `__str__` 方法，该方法使用f字符串来格式化属性并返回结果。如果测试刚创建的`Date` 对对象，结果应该是 `1933-06-22` 。\n",
        "4. 编写一个名为 `is_after`的函数,接受两个 `Date` 对象,如果第一个在第二个之后则返回 `True` 。创建表示1933年9月17日的第二个对象，并检查它是否在第一个对象之后。\n",
        "\n",
        "Hint: You might find it useful write a method called `to_tuple` that returns a tuple that contains the attributes of a `Date` object in year-month-day order.\n",
        "\n",
        "提示: 编写名为 `to_tuple` 的函数,返回包含 `Date` 对象年-月-日的元组,对于日期的比较很有用。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3c9f3777-4869-481e-9f4e-4223d6028913",
      "metadata": {},
      "outputs": [],
      "source": [
        "# 在这作答"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1122620d-f3f6-4746-8675-13ce0b7f3ee9",
      "metadata": {
        "tags": []
      },
      "source": [
        "使用这些示例来测试。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 38,
      "id": "fd4b2521-aa71-45da-97eb-ce62ce2714ad",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "birthday1 = Date(1933, 6, 22)\n",
        "print(birthday1)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 39,
      "id": "ee3f1294-cad1-406b-a574-045ad2b84294",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "birthday2 = Date(1933, 9, 17)\n",
        "print(birthday2)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "ac093f7b-83cf-4488-8842-5c71bcfa35ec",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "birthday1.is_after(birthday2)  # 应为False"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "7e7cb5e1-631f-4b1e-874f-eb16d4792625",
      "metadata": {
        "tags": []
      },
      "outputs": [],
      "source": [
        "birthday2.is_after(birthday1)  # 应为True"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "14351b4b",
      "metadata": {},
      "source": [
        "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n",
        "\n",
        "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n",
        "\n",
        "Code license: [MIT License](https://mit-license.org/)\n",
        "\n",
        "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)"
      ]
    }
  ],
  "metadata": {
    "celltoolbar": "Tags",
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.11.11"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}
